第4回 Spring環境におけるDBアクセス(1) 〜 JdbcTemplate篇
よく訓練されたアップル信者、都元です。では今回は、前回の予告どおり、Spring環境からのDBアクセスについて見て行こうと思います。
DB環境整備(MySQL)
というわけで、今回はDBを使いますので、ローカルにMySQLをインストールしておいてください。筆者の検証環境におけるMySQLのバージョンは5.6系(Server version: 5.6.13-log Source distributionという奴)ですが、まぁまだ基本的なことしかしませんので、最新でなくても良いと思います。また、localhostからはパスワード無しでrootユーザで接続できるような環境を前提としています。適宜そのように調整するか、パスワードが必要な環境を前提とするのであれば、適宜読み替えをおこなってください。
さて、ではMySQLに接続して、とりあえずDBとスキーマを作り、適当なデータを投入しておきましょう。
CREATE DATABASE berserker CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; USE berserker; CREATE TABLE users ( username VARCHAR(32) PRIMARY KEY, password CHAR(60) NOT NULL ); CREATE TABLE events ( event_id BIGINT AUTO_INCREMENT PRIMARY KEY, event_name VARCHAR(128) NOT NULL, event_owner VARCHAR(32), FOREIGN KEY (event_owner) REFERENCES users (username) ); CREATE TABLE participations ( username VARCHAR(32), event_id BIGINT, PRIMARY KEY (username, event_id), FOREIGN KEY (username) REFERENCES users (username), FOREIGN KEY (event_id) REFERENCES events (event_id) ); CREATE TABLE pictures ( picture_id BIGINT AUTO_INCREMENT PRIMARY KEY, location VARCHAR(255) NOT NULL, event_id BIGINT NOT NULL, FOREIGN KEY (event_id) REFERENCES events (event_id) ); INSERT INTO users (username, password) VALUES ('miyamoto', '$2a$10$cPnF0sq.bCPHeGuzVagOgOmbe2spT1Uh1k9LyuS0jzb5F3Lm.9kEy'), ('yokota', '$2a$10$nkvNPCb3Y1z/GSearD7s7OBdS9BoLBss3D4enbFQIgNJDvr0Xincm');
ちなみに、DBに生パスワードを保存するという設計は、やってはいけません。上記はエンコード(BCrypt)によって加工したパスワードを記述しています。
依存ライブラリ定義
前回、せっかくGradleを導入しましたので、そのファイルに修正を入れます。まずライブラリのバージョンは、このようにgradle.properties内で変数として参照できるようように定義しておくと、バージョンアップや他の依存ライブラリ追加の際に楽ができます。
springVersion = 4.1.5.RELEASE mysqlVersion = 5.1.36
Springの追加ライブラリとしては2つです。DBのトランザクション制御を担うspring-tx、JDBCをラップする各種クラスを提供するspring-jdbcです。これの他に、MySQLを使いますので、MySQLのドライバを追加しておきましょう。
dependencies { // spring compile "org.springframework:spring-context-support:$springVersion" compile "org.springframework:spring-tx:$springVersion" // 追加1 compile "org.springframework:spring-jdbc:$springVersion" // 追加2 // other // ... compile "mysql:mysql-connector-java:$mysqlVersion" // 追加3 }
Springの設定とJava側の準備
データベース設定: DataSourceConfiguration
SpringでDBを扱う場合、大抵はjavax.sql.DataSourceの実装を使うのが基本になります。今回は最もシンプルな実装として、DriverManagerDataSourceというのを使います。
DataSourceConfigurationというクラスを作成し、以下のような内容を記述します。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @EnableTransactionManagement public class DataSourceConfiguration { @Bean public DriverManagerDataSource dataSource() { DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource(); driverManagerDataSource.setDriverClassName(com.mysql.jdbc.Driver.class.getName()); driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/berserker"); driverManagerDataSource.setUsername("root"); driverManagerDataSource.setPassword(""); return driverManagerDataSource; } @Bean public DataSourceTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } @Bean public JdbcTemplate jdbcTemplate() { return new JdbcTemplate(dataSource()); } }
DriverManagerDataSourceに対してドライバやURL等、接続に必要な情報を与えます。続いて、トランザクションの制御を行うtransactionManagerを定義 *1し、アノテーションによる「宣言的トランザクション制御」を有効にします。最後に、jdbcTemplateというbeanを定義していますが、クライアントのプログラムからはこのbeanを利用します。
今回のプログラム側の基本骨格
以上を作成の上、メインのクラスを書いていきましょう。今回はDBへのアクセスを確認するだけの目的ですので、単純なmainメソッドを起点とするコンソールプログラムです。以前にもご紹介した手法ですが、DataAccessSampleをbeanとして扱い、このインスタンスにDIが可能な状況を作り出しています。
@Component @ComponentScan public class DataAccessSample { public static void main(String[] args) { try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(DataAccessSample.class)) { DataAccessSample das = context.getBean(DataAccessSample.class); das.execute(); } } @Autowired JdbcTemplate jdbcTemplate; @Transactional public void execute() { // do some work } }
ポイントは2つ。1つ目はDataDourceConfigurationで定義したjdbcTemplateをDIしていること。もう1つはexecuteメソッドに@Transactionalというアノテーションを付けていることです。
@Transactioanlアノテーションは、トランザクション制御を宣言的に行う手法です。詳しくは次回以降にお話しますが、executeの実行直前にトランザクションを開始(begin)させ、実行直後にコミットします。executeが正常に終了せず、例外によって終了した場合はロールバックします。つまり、executeメソッド内のDB処理がアトミックになるわけです。
ということを、「手続きの記述」 *2ではなく、メソッドにアノテーションを付与するという「宣言の記述」で表現しようとする試み *3です。
あとは、usersテーブルに対応するJava beansを作っておきましょう。
@ToString public class User { @Getter @Setter private String username; @Getter @Setter private String password; }
いよいよDBアクセス
あとはexecuteの中からjdbcTemplateのメソッドを呼び出すだけです。まずは簡単なところから。usersテーブルの全件数を取ってみましょう。第1引数がクエリで、第2引数は戻り値の型となります。結果的に2が返るはずです。
Long allUsersCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", Long.class); System.out.println("allUsersCount = " + allUsersCount);
つづいて、miyamotoユーザのパスワードを取得。第2引数には、クエリ内に埋め込んだプレースホルダ?に割り当てるパラメータを配列で渡せます。結果は$2a$10$cPnF0sq.bCPHeGuzVagOgOmbe2spT1Uh1k9LyuS0jzb5F3Lm.9kEy。
String password = jdbcTemplate.queryForObject("SELECT password FROM users WHERE username = ?", new Object[] { "miyamoto" }, String.class); System.out.println("password = " + password);
ここまでは1カラムから構成される単純な値(いわゆるスカラー値)を取得するだけでした。複数のカラムがある場合は、RowMapperというのを使います。
User user = jdbcTemplate.queryForObject("SELECT * FROM users WHERE username = ?", new Object[] { "miyamoto" }, new RowMapper<User>() { @Override public User mapRow(ResultSet rs, int rowNum) throws SQLException { User user = new User(); user.setUsername(rs.getString("username")); user.setPassword(rs.getString("password")); return user; } }); System.out.println("user = " + user);
これで、ResultSetから取得できる各カラムの値をUserクラスのフィールドにマッピングできました。
サンプルプロジェクト berserker v4.0
2015-07-13追記:このコードを、GitHubに上げておきました。ご興味のある方は、下記のように実行してみてください。初回実行時には色々表示も異なり、実行開始まで時間が掛かります。execute1タスクが、Mainクラスの実行、execute2タスクがSpringMainクラスの実行です。ログ出力は少々異なりますが、どちらもfooという出力がされていますね。
$ git clone https://github.com/classmethod-sandbox/berserker.git $ cd berserker $ git checkout 4.0 $ ./gradlew execute :compileJava UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :execute 2016/04/08 18:43:46.773 [main] INFO o.s.c.a.AnnotationConfigApplicationContext:578 - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@511baa65: startup date [Fri Apr 08 18:43:46 JST 2016]; root of context hierarchy 2016/04/08 18:43:47.253 [main] INFO o.s.j.d.DriverManagerDataSource:133 - Loaded JDBC driver: com.mysql.jdbc.Driver 2016/04/08 18:43:47.697 [main] INFO j.c.e.berserker.DataAccessSample:56 - allUsersCount = 2 2016/04/08 18:43:47.719 [main] INFO j.c.e.berserker.DataAccessSample:61 - password = $2a$10$cPnF0sq.bCPHeGuzVagOgOmbe2spT1Uh1k9LyuS0jzb5F3Lm.9kEy 2016/04/08 18:43:47.723 [main] INFO j.c.e.berserker.DataAccessSample:75 - user = User(username=miyamoto, password=$2a$10$cPnF0sq.bCPHeGuzVagOgOmbe2spT1Uh1k9LyuS0jzb5F3Lm.9kEy) 2016/04/08 18:43:47.728 [main] INFO o.s.c.a.AnnotationConfigApplicationContext:960 - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@511baa65: startup date [Fri Apr 08 18:43:46 JST 2016]; root of context hierarchy BUILD SUCCESSFUL Total time: 2.674 secs
まとめ
と、いうのがSpringが標準で用意しているデータアクセスの基本です。もう少し使いやすい工夫がされたMappingSqlQuery等の仕組みもありますが、もう少しコードの記述がしやすくなるだけで、基本的な考え方は変わりません。ここまで見て、少々原始的(低級)な印象が強いのではないでしょうか。生のJDBCを操作するよりだいぶましですが、Javaコードの中にSQLを記述すること自体、あまりモダンな感じがしません。
しかし、これ以上の高級なAPIを利用したい場合は、Spring単独ではなく、外部のライブラリと連携する必要があります。代表的な選択肢としては、Hibernate、JDO、JPA、iBatis等があります。Springはいずれの選択肢についても、インテグレーションのサポートを提供しています。
さらに、Spring DataというSpringのサブプロジェクトを利用するという選択肢もあります。筆者は個人的にこちらの方が好みなので、次回は Spring Data についてのお話をしようと思います。
という訳で、次回は「第5回 Spring環境におけるDBアクセス(2) 〜 Spring Data篇」です。実は、データアクセスの仕組みはこっちが本命。正直JdbcTemplateとか、私はあんま使わないですよwww(ごめんなさいごめんなさいwww *4